﻿using System;
using System.Collections.Generic;
using System.Linq;
using BFlatCompile.Nodes;

namespace BFlatCompile.Serializer
{
	// Phase 2: Add static initializer functions for each class
	internal static class Phase02AddStaticInitializers
	{
		public static void Do(IList<Namespace> namespaces, Dictionary<ClassInitFinalizer, Method> classInitFinalizationMarkers)
		{
			// These are actually separate than the static constructor. The static initializer does the following:
			// * Spoofs a reference to the base class and nested parent. This will invoke the static initializers for these
			//   before the initialization of this class. This makes the logic of initialization order much easier for the
			//   interpreter.
			// * Initializes all the static fields.
			// * Makes a call to the static constructor if present.
			// * Returns the instance of the class
			// The static initializer will be called with the class definition as the context, so "this" should be used for all
			// of the class references. This also prevents circular intiialization. 
			// These static initializers will be invoked when the byte code for class definition insertion is called when the 
			// class it refers to has not been initialized.
			foreach (Class c in GetAllClasses(namespaces))
			{
				CreateStaticInitializer(c, classInitFinalizationMarkers);
			}
		}

		private static void CreateStaticInitializer(Class cls, Dictionary<ClassInitFinalizer, Method> classInitFinalizationMarkers)
		{
			List<Executable> code = new List<Executable>();

			// TODO: determine what order C# does this: base class first or nested parent first. 
			// I may have this backwards and am too tired to think of what foul implications there could be if I get this wrong. 
			Class parentNestedClass = cls.Parent as Class;
			if (parentNestedClass != null)
			{
				code.Add(new ExpressionAsExecutable(new Variable(parentNestedClass.Name, null)));
			}
			Class baseClass = cls.BaseClassInstance;
			if (baseClass != null)
			{
				code.Add(new ExpressionAsExecutable(new Variable(baseClass.Name, null)));
			}

			foreach (Field field in cls.Fields.Where<Field>(field => field.IsStatic))
			{
				Expression defaultValue = field.DefaultValue ?? NullConstant.Instance;
				// TODO: replace this with the this.field assign op
				code.Add(new Assignment(new DotDereference(new ThisValue(null), field.Name, null), defaultValue, '=', null));
			}

			if (cls.Constructors.Where<Constructor>(cons => cons.IsStatic).Count<Constructor>() != 0)
			{
				code.Add(new InvokeStaticConstructor());
			}

			// Initialization is complete. Set status from 1 to 2.
			ClassInitFinalizer cif = new ClassInitFinalizer(cls.ID);
			code.Add(cif);

			Method init = new Method(null, true, new MethodParam[0], code, "[Generated class initializer code]"); // This will append return
			cls.AddInit(init);

			classInitFinalizationMarkers[cif] = init;
		}

		private static Class[] GetAllClasses(IList<Namespace> namespaces)
		{
			List<Class> output = new List<Class>();
			List<object> traversal = new List<object>(namespaces);
			while (traversal.Count > 0)
			{
				Namespace ns = traversal[0] as Namespace;
				Class c = traversal[0] as Class;
				traversal.RemoveAt(0);
				if (c != null)
				{
					output.Add(c);
					traversal.AddRange(c.NestedClasses);
				}
				else if (ns != null)
				{
					traversal.AddRange(ns.Classes);
					traversal.AddRange(ns.Namespaces);
				}
				else
				{
					throw new Exception("This shouldn't happen.");
				}
			}

			return output.ToArray();
		}
	}
}
